//******************************************************************************
//this code is protected by the GNU affero GPLv3
//author:Sylvain BERTRAND <sylvain.bertrand AT gmail dot com>
//                        <digital.ragnarok AT gmail dot com>
//******************************************************************************
#define CLIENT_C
#include "globals.h"
#undef CLIENT_C

static i ep_fd;

static void client_so_setup(i so,s32 slot)
{
  struct epoll_event ep_evt;
  memset(&ep_evt,0,sizeof(ep_evt));
  ep_evt.events=EPOLLIN|EPOLLHUP|EPOLLPRI;//*level* triggered
  ep_evt.data._32=(u32)slot;

  l r=epoll_ctl(ep_fd,EPOLL_CTL_ADD,so,&ep_evt);
  if(ISERR(r)){
    PERR("fatal(%ld):unable to register client socket(%d) to epoll\n",r,so);
    exit(LWL_ERR);
  }
}

void client_obj_generic_release(struct client *c,s32 o)
{
    c->objs[o].state=CLIENT_OBJ_INVALID;
}

void client_obj_ref(struct client *c,s32 o)
{
  ++c->objs[o].n;
}

s32 client_obj_unref(struct client *c,s32 o)
{
  if(c->objs[o].state&CLIENT_OBJ_INVALID) goto exit;
  if(c->objs[o].n&&--c->objs[o].n) goto exit;
  if(c->objs[o].release) c->objs[o].release(c,o);
exit:
  return c->objs[o].n;
}

static s8 req_obj_route(struct client *c,struct msg *m)
{
  s8 r=LWL_OK;
  s32 slot=CLIENT_FIRST_VALID_ID;
  while(1){
    if(slot==CLIENT_OBJS_MAX) break;
    if(!(c->objs[slot].state&CLIENT_OBJ_INVALID)
         &&c->objs[slot].state&CLIENT_OBJ_VISIBLE&&c->objs[slot].id==m->req[0]){
      if(c->objs[slot].state&CLIENT_OBJ_REGISTRY_MAP)
        r=registry_map_req(c,slot,m);
      else if(c->objs[slot].state&CLIENT_OBJ_SHM_POOL)
        r=wl_shm_pool_req(c,slot,m);
      else if(c->objs[slot].state&CLIENT_OBJ_SHM_POOL_BUFFER)
        r=wl_shm_pool_buffer_req(c,slot,m);
      else if(c->objs[slot].state&CLIENT_OBJ_SURFACE)
        r=wl_surface_req(c,slot,m);
      else if(c->objs[slot].state&CLIENT_OBJ_REGION)
        r=wl_region_req(c,slot,m);
      else if(c->objs[slot].state&CLIENT_OBJ_SHELL_SURFACE)
        r=wl_shell_surface_req(c,slot,m);
      else if(c->objs[slot].state&CLIENT_OBJ_POINTER)
        r=wl_pointer_req(c,slot,m);
      else if(c->objs[slot].state&CLIENT_OBJ_KEYBOARD)
        r=wl_keyboard_req(c,slot,m);
      goto exit;
    }
    ++slot;
  }

  PERR("client(%d):unhandled req:0x%08x 0x%08x\n",c->so,m->req[0],m->req[1]);

exit:
  return r;
}

static s8 cm_parse(struct client *c,struct msg *m,
                              s8 (*parse)(struct client *c,struct msg *m,i *fd))
{
  struct cmsg_hdr *cm_hdr=CMSG_FIRSTHDR(&m->hdr);
  while(1){
    if(cm_hdr==0) break;
    if(cm_hdr->lvl!=SOL_SOCKET||cm_hdr->type!=SCM_RIGHTS) continue;

    u32 cm_data_sz=cm_hdr->len-CMSG_LEN(0);
    if(cm_data_sz%sizeof(i)){
      PERR("fatal:ancillary item data of socket message is not aligned on file"
                                    " descriptor size from client(%d)\n",c->so);
      exit(LWL_ERR);
    }
    i *fd=CMSG_DATA(cm_hdr);
    while(fd<(i*)(CMSG_DATA(cm_hdr)+cm_data_sz)){
      s8 r=parse(c,m,fd);
      if(r==LWL_ERR) return r;
      ++fd;
    }
    cm_hdr=CMSG_NXTHDR(&m->hdr,cm_hdr);
  }
  return LWL_OK;
}

static s8 cm_parse_decode(struct client *c,struct msg *m,i *fd)
{
  s8 r=LWL_OK;
  if(m->fd==MSG_FDS_MAX){//we use the m->fd index to count the fds
    PERR("client(%d):fatal:ancillary data has too many file descriptors\n",
                                                                         c->so);
    r=LWL_ERR;
  }else m->fds[m->fd++]=*fd;
  return r;
}

static s8 cm_parse_fds_close(struct client *c,struct msg *m,i *fd)
{
  (void)c;(void)m;
  l r;
  do r=close(*fd); while(r==-EINTR);
  return LWL_OK;
}
 
static s8 cm_decode(struct client *c,struct msg *m)
{
  s8 r=cm_parse(c,m,cm_parse_decode);
  if(r==LWL_ERR) m->fd=0;//rewind the fd index
  return r;
}

static void cm_fds_close(struct client *c,struct msg *m)
{
  cm_parse(c,m,cm_parse_fds_close);
}

static s8 msg_demux(struct client *c,struct msg *m)
{
  s8 r=cm_decode(c,m);
  if(r==LWL_ERR) goto exit;

  m->req=(u32*)&m->buf[0];

  while(1){
    if(m->req+2>((u32*)&m->buf[0])+m->dws){
      PERR("client(%d):fatal:request cross message boundaries\n",c->so);
      r=LWL_ERR;goto exit;
    }
    s16 req_sz=m->req[1]>>16;
    m->req_op=m->req[1]&0xffff;

    if(req_sz&0x3){
      PERR("client(%d):fatal:request has a payload size not 32 bits word"
                                                            " aligned\n",c->so);
      r=LWL_ERR;goto exit;
    }

    m->req_dws=req_sz>>2;
    if(m->req_dws<2||m->req+m->req_dws>((u32*)&m->buf[0])+m->dws){
      PERR("client(%d):fatal:request cross message boundaries or has an invalid"
                                                               " size\n",c->so);
      r=LWL_ERR;goto exit;
    }

    r=req_obj_route(c,m);//request is at least 2 dws and in mem
    if(r==LWL_ERR) goto exit;

    m->req+=m->req_dws;
    if(m->req>=((u32*)&m->buf[0])+m->dws) break;
  }

exit:
  return r;
}

s8 client_rd(struct client *c)
{
  static struct msg m;
  memset(&m,0,sizeof(m));
  for(u32 j=0;j<MSG_FDS_MAX;++j) m.fds[j]=-1;

  struct io_vec iov;
  iov.base=&m.buf[0];
  iov.len=MSG_BUF_SZ;
  m.hdr.iov=&iov;
  m.hdr.iov_len=1;
 
  //use a control message for file descriptors
  m.hdr.ctl=&m.cm_buf[0];
  m.hdr.ctl_len=sizeof(m.cm_buf);
  struct cmsg_hdr *cm_hdr=CMSG_FIRSTHDR(&m.hdr);
  cm_hdr->lvl=SOL_SOCKET;
  cm_hdr->type=SCM_RIGHTS;
  cm_hdr->len=CMSG_LEN(sizeof(i));

  l r;//client *blocking* socket
  while(1){r=recvmsg(c->so,&m.hdr,0); if(r!=-EINTR) break;}
  if(ISERR(r)){
    PERR("client(%d):fatal(%ld):error receiving request\n",c->so,r);
    r=LWL_ERR;goto exit;
  }

  if(m.hdr.flgs&MSG_CTRUNC){
    PERR("client(%d):fatal:error receiving request file descriptor\n",c->so); 
    r=LWL_ERR;goto exit;
  }

  if(r&0x3){//payload must be padded on 32 bits words
    PERR("client(%d):fatal:receiving request has a payload size not 32 bits"
                                                       " word aligned\n",c->so);
    r=LWL_ERR;goto exit;
  }

  if(!r){
    PERR("client(%d):fatal:received empty request\n",c->so);
    r=LWL_ERR;goto exit;
  }

  m.dws=r>>2;

  r=msg_demux(c,&m);//message size is at least 1 dw 

exit:
  if(r==LWL_ERR) cm_fds_close(c,&m);
  return (s8)r;
}

void client_kill(struct client *c)
{
  l r=epoll_ctl(ep_fd,EPOLL_CTL_DEL,c->so,0);
  if(ISERR(r)){
    PERR("fatal(%ld):unable to delete client socket(%d) from epoll(%d)\n",r,
                                                                   c->so,ep_fd);
    exit(LWL_ERR);
  }

  s32 slot=0;
  while(1){
    if(slot==CLIENT_OBJS_MAX) break;
    if(!c->objs[slot].state&CLIENT_OBJ_INVALID) c->objs[slot].release(c,slot);
    ++slot;
  }

  while(1){r=close(c->so); if(r!=-EINTR) break;}
  c->so=-1;
}

void clients_init(i srv_ep_fd)
{
  ep_fd=srv_ep_fd;
  s32 c=0;
  while(1){
    if(c==CLIENTS_MAX) break;
    clients[c].so=-1;
    s32 o=0;
    while(1){
      if(o==CLIENT_OBJS_MAX) break;
      clients[c].objs[o].state=CLIENT_OBJ_INVALID;
      ++o;
    }
    ++c;
  }
}

s8 client_new(i so)
{
  s32 slot=0;
  while(1){
    if(slot==CLIENTS_MAX) return LWL_ERR;
    if(clients[slot].so==-1) break;
    ++slot;
  }

  client_so_setup(so,slot);
  clients[slot].so=so;
  registry_map_new_visible(&clients[slot],DISPLAY_NAME,WL_DISPLAY_ID);
  return LWL_OK;
}

void client_registry_publish(struct client *c, s32 name)
{
    s32 o_idx=0;
    while(1){
      if(o_idx==CLIENT_OBJS_MAX) break;
      struct client_obj *o=&c->objs[o_idx];
      if(!(o->state&CLIENT_OBJ_INVALID)&&o->state&CLIENT_OBJ_REGISTRY_MAP)
        if(o->map.name==REGISTRY_NAME){
          s8 r=wl_registry_ev_global(c,o->id,name);
          if(r==LWL_ERR){
            client_kill(c);//naughty client, kill it
            return;
          }
	}
      ++o_idx;
    }
}

void client_registry_unpublish(struct client *c, s32 name)
{
    s32 o_idx=0;
    while(1){
      if(o_idx==CLIENT_OBJS_MAX) break;
      struct client_obj *o=&c->objs[o_idx];
      if(!(o->state&CLIENT_OBJ_INVALID)&&o->state&CLIENT_OBJ_REGISTRY_MAP)
        if(o->map.name==REGISTRY_NAME){
          s8 r=wl_registry_ev_global_remove(c,o->id,name);
          if(r==LWL_ERR){
            client_kill(c);//naughty client, kill it
            return;
          }
	}
      ++o_idx;
    }
}
